@rubytech/create-realagent 1.0.856 → 1.0.859

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/__tests__/cdp-port-no-silent-fallback.test.js +53 -0
  2. package/dist/index.js +58 -22
  3. package/dist/port-resolution.js +1 -1
  4. package/package.json +2 -2
  5. package/payload/platform/plugins/admin/hooks/pre-tool-use.sh +2 -2
  6. package/payload/platform/plugins/admin/mcp/dist/index.js +20 -10
  7. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  8. package/payload/platform/plugins/admin/mcp/dist/lib/neo4j.js +1 -1
  9. package/payload/platform/plugins/admin/mcp/dist/lib/neo4j.js.map +1 -1
  10. package/payload/platform/plugins/cloudflare/references/manual-setup.md +1 -1
  11. package/payload/platform/plugins/cloudflare/scripts/_stream-log.sh +1 -1
  12. package/payload/platform/plugins/cloudflare/scripts/setup-tunnel.sh +36 -9
  13. package/payload/platform/plugins/contacts/mcp/dist/lib/neo4j.js +1 -1
  14. package/payload/platform/plugins/contacts/mcp/dist/lib/neo4j.js.map +1 -1
  15. package/payload/platform/plugins/email/mcp/dist/lib/neo4j.js +1 -1
  16. package/payload/platform/plugins/email/mcp/dist/lib/neo4j.js.map +1 -1
  17. package/payload/platform/plugins/email/mcp/dist/scripts/email-auto-respond.js +1 -1
  18. package/payload/platform/plugins/email/mcp/dist/scripts/email-auto-respond.js.map +1 -1
  19. package/payload/platform/plugins/memory/mcp/dist/lib/neo4j.js +1 -1
  20. package/payload/platform/plugins/memory/mcp/dist/lib/neo4j.js.map +1 -1
  21. package/payload/platform/plugins/scheduling/mcp/dist/lib/neo4j.js +1 -1
  22. package/payload/platform/plugins/scheduling/mcp/dist/lib/neo4j.js.map +1 -1
  23. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js +1 -1
  24. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js.map +1 -1
  25. package/payload/platform/plugins/tasks/mcp/dist/lib/neo4j.js +1 -1
  26. package/payload/platform/plugins/tasks/mcp/dist/lib/neo4j.js.map +1 -1
  27. package/payload/platform/plugins/waitlist/mcp/dist/lib/neo4j.js +1 -1
  28. package/payload/platform/plugins/waitlist/mcp/dist/lib/neo4j.js.map +1 -1
  29. package/payload/platform/plugins/workflows/mcp/dist/lib/neo4j.js +1 -1
  30. package/payload/platform/plugins/workflows/mcp/dist/lib/neo4j.js.map +1 -1
  31. package/payload/platform/scripts/check-no-task-id-leaks.mjs +110 -0
  32. package/payload/platform/scripts/test-laptop-vnc-boot.sh +8 -1
  33. package/payload/platform/scripts/vnc.sh +40 -34
  34. package/payload/server/chunk-22LK7D5R.js +1612 -0
  35. package/payload/server/chunk-2Q2S52GB.js +10906 -0
  36. package/payload/server/chunk-7ADUQXTU.js +2143 -0
  37. package/payload/server/chunk-BY4LZDL4.js +667 -0
  38. package/payload/server/chunk-CNNPNADU.js +10891 -0
  39. package/payload/server/chunk-FL3H3AQD.js +1603 -0
  40. package/payload/server/client-pool-3BCJTPPA.js +34 -0
  41. package/payload/server/client-pool-WA5WGN7W.js +34 -0
  42. package/payload/server/cloudflare-task-tracker-OOQCL5ZB.js +20 -0
  43. package/payload/server/maxy-edge.js +34 -6
  44. package/payload/server/public/assets/{admin-CZpefPcA.js → admin-BumnnEDn.js} +60 -60
  45. package/payload/server/public/index.html +1 -1
  46. package/payload/server/server.js +202 -134
@@ -0,0 +1,53 @@
1
+ // Task 959 — structural gate against the recurrence-class
2
+ // silent-fallback-masks-root-cause violation. The four runtime sites that
3
+ // previously substituted `9222 + offset` for a missing brand.json.cdpPort
4
+ // (paths.ts, admin/mcp/index.ts, vnc.sh, test-laptop-vnc-boot.sh) plus the
5
+ // installer-side brand stamp at packages/create-maxy/src/index.ts have all
6
+ // been swept to loud-fail. This test asserts the three greps from criterion
7
+ // 2 of the task brief return zero matches; reintroducing any silent fallback
8
+ // fails CI immediately.
9
+ //
10
+ // Compiles to dist/__tests__/cdp-port-no-silent-fallback.test.js and runs
11
+ // via `node --test 'dist/__tests__/*.test.js'` per package.json's test
12
+ // script. Greps run via `git grep` rooted at the repo top-level (resolved
13
+ // relative to dist/__tests__/'s known offset) so the test is invariant to
14
+ // the caller's cwd.
15
+ import test from "node:test";
16
+ import assert from "node:assert/strict";
17
+ import { spawnSync } from "node:child_process";
18
+ import { resolve } from "node:path";
19
+ import { fileURLToPath } from "node:url";
20
+ const SELF_DIR = fileURLToPath(new URL(".", import.meta.url));
21
+ // dist/__tests__/ → repo root is four levels up
22
+ // (packages/create-maxy/dist/__tests__ → packages/create-maxy → packages → repo).
23
+ const REPO_ROOT = resolve(SELF_DIR, "..", "..", "..", "..");
24
+ function grepReturns(pattern, includes) {
25
+ const args = ["-RnE", pattern];
26
+ for (const inc of includes)
27
+ args.push(`--include=${inc}`);
28
+ args.push("platform/", "packages/");
29
+ // Exclude this test file (it embeds the patterns as string literals,
30
+ // which would otherwise self-match) and exclude the gitignored payload
31
+ // mirror generated by `npm run bundle`.
32
+ args.push("--exclude-dir=payload");
33
+ args.push("--exclude=cdp-port-no-silent-fallback.test.*");
34
+ const out = spawnSync("grep", args, { cwd: REPO_ROOT, encoding: "utf8" });
35
+ // grep exit 1 = no matches (success for this test); exit 0 = matches found
36
+ // (failure); exit 2 = grep error (test infrastructure fault).
37
+ if (out.status !== 0 && out.status !== 1) {
38
+ throw new Error(`grep error (status=${out.status}): ${out.stderr}`);
39
+ }
40
+ return (out.stdout ?? "").trim();
41
+ }
42
+ test("Task 959 grep 1: no `9222 + offset` arithmetic in source files", () => {
43
+ const matches = grepReturns("9222\\s*\\+\\s*\\w*offset", ["*.ts", "*.tsx", "*.sh"]);
44
+ assert.equal(matches, "", `silent-fallback regression — '9222 + offset' arithmetic reintroduced:\n${matches}`);
45
+ });
46
+ test("Task 959 grep 2: no jq `cdpPort // 9222` fallback in shell or TS", () => {
47
+ const matches = grepReturns("cdpPort\\s*//\\s*9222", ["*.sh", "*.ts"]);
48
+ assert.equal(matches, "", `silent-fallback regression — jq cdpPort // 9222 fallback reintroduced:\n${matches}`);
49
+ });
50
+ test("Task 959 grep 3: no `?? 9222` or `|| 9222` nullish-coalesce in TS", () => {
51
+ const matches = grepReturns("\\?\\?\\s*9222|\\|\\|\\s*9222", ["*.ts"]);
52
+ assert.equal(matches, "", `silent-fallback regression — '?? 9222' or '|| 9222' coalesce reintroduced:\n${matches}`);
53
+ });
package/dist/index.js CHANGED
@@ -473,7 +473,7 @@ function ensureNonSnapChromium() {
473
473
  throw new Error(`ensureNonSnapChromium: ${decision.reason}. apt install of \`chromium\` ran in installAptGroup(VNC stack) above; if its post-check passed but no chromium binary is on PATH, the system PATH is misconfigured.`);
474
474
  }
475
475
  if (decision.action === "install-google-chrome") {
476
- console.log(" Detected snap-confined Chromium (Task 929) — installing Google Chrome stable...");
476
+ console.log(" Detected snap-confined Chromium — installing Google Chrome stable...");
477
477
  logFile(` [snap-chromium] installing google-chrome-stable from Google's signed apt repo`);
478
478
  // Fetch + dearmor the signing key, write to /etc/apt/trusted.gpg.d/. Pipe
479
479
  // composition runs through bash -c so the curl|gpg pipeline is one
@@ -495,7 +495,7 @@ function ensureNonSnapChromium() {
495
495
  ], { sudo: true });
496
496
  console.log(" [privileged] apt-get update");
497
497
  shell("apt-get", ["update"], { sudo: true });
498
- installAptGroup("Google Chrome stable (Task 929)", ["google-chrome-stable"]);
498
+ installAptGroup("Google Chrome stable", ["google-chrome-stable"]);
499
499
  // Re-resolve after install to capture the now-installed absolute path.
500
500
  const postInstallWhich = which("google-chrome-stable");
501
501
  if (!postInstallWhich) {
@@ -518,7 +518,7 @@ function ensureNonSnapChromium() {
518
518
  // surfaces the contract breach with the install context still in scope.
519
519
  const finalRealpath = realpath(RESOLVED_CHROMIUM_BIN);
520
520
  if (isSnapConfinedPath(finalRealpath)) {
521
- throw new Error(`ensureNonSnapChromium: resolved Chromium binary ${RESOLVED_CHROMIUM_BIN} realpaths to ${finalRealpath} which is under /snap/ — refusing to persist (Task 929).`);
521
+ throw new Error(`ensureNonSnapChromium: resolved Chromium binary ${RESOLVED_CHROMIUM_BIN} realpaths to ${finalRealpath} which is under /snap/ — refusing to persist.`);
522
522
  }
523
523
  console.log(` Chromium binary: ${RESOLVED_CHROMIUM_BIN} (realpath=${finalRealpath ?? "?"})`);
524
524
  logFile(` [snap-chromium] resolved bin=${RESOLVED_CHROMIUM_BIN} realpath=${finalRealpath ?? "null"}`);
@@ -1171,7 +1171,7 @@ function peerBrandUsingSystemUnit() {
1171
1171
  peerEnvContents.push([hostname, readFileSync(envPath, "utf-8")]);
1172
1172
  }
1173
1173
  catch (err) {
1174
- console.error(` WARNING: unable to read peer brand .env at ${envPath} — treating as potential dependency to avoid data loss (Task 800): ${err instanceof Error ? err.message : String(err)}`);
1174
+ console.error(` WARNING: unable to read peer brand .env at ${envPath} — treating as potential dependency to avoid data loss: ${err instanceof Error ? err.message : String(err)}`);
1175
1175
  return hostname;
1176
1176
  }
1177
1177
  }
@@ -1302,7 +1302,7 @@ WantedBy=multi-user.target
1302
1302
  // mutually exclusive with the disable path: exactly one log line per install.
1303
1303
  const peerOnSystemUnit = peerBrandUsingSystemUnit();
1304
1304
  if (peerOnSystemUnit !== null) {
1305
- const keptActiveMsg = ` [neo4j] system unit kept active — peer brand ${peerOnSystemUnit} depends on port ${DEFAULT_NEO4J_PORT} (Task 800)`;
1305
+ const keptActiveMsg = ` [neo4j] system unit kept active — peer brand ${peerOnSystemUnit} depends on port ${DEFAULT_NEO4J_PORT}`;
1306
1306
  console.log(keptActiveMsg);
1307
1307
  logFile(keptActiveMsg);
1308
1308
  }
@@ -1931,12 +1931,36 @@ function setupVncViewer() {
1931
1931
  } catch (e) { /* swallow */ }
1932
1932
  }
1933
1933
 
1934
+ // Layer-6 beacon (Task 958): emit event=rfb-connected and
1935
+ // event=rfb-error to the operator-grep lifecycle endpoint so
1936
+ // server.log shows the noVNC outcome alongside [setup-tunnel] /
1937
+ // [cloudflare-setup] / [device-url:click] / [http] / [websockify].
1938
+ // Same-origin POST works whether the iframe is parented by the
1939
+ // React BrowserViewer or by vnc-popout.html.
1940
+ function reportBrowserViewerEvent(event, fields) {
1941
+ try {
1942
+ var body = JSON.stringify(Object.assign(
1943
+ { event: event, surface: 'iframe' },
1944
+ fields || {},
1945
+ ));
1946
+ fetch('/api/admin/browser-iframe/event', {
1947
+ method: 'POST',
1948
+ credentials: 'same-origin',
1949
+ headers: { 'Content-Type': 'application/json' },
1950
+ body: body,
1951
+ keepalive: true,
1952
+ }).catch(function() { /* swallow */ });
1953
+ } catch (e) { /* swallow */ }
1954
+ }
1955
+ var connectStartedAt = 0;
1956
+
1934
1957
  function connect() {
1935
1958
  status.classList.remove('hidden');
1936
1959
  status.querySelector('.status-spinner').style.display = '';
1937
1960
  status.querySelector('div:nth-child(2)').textContent =
1938
1961
  retryCount > 0 ? 'Reconnecting… (' + retryCount + ')' : 'Connecting to browser…';
1939
1962
  status.querySelector('.status-reason').textContent = '';
1963
+ connectStartedAt = Date.now();
1940
1964
 
1941
1965
  const rfb = new RFB(screen, wsUrl);
1942
1966
  rfb.scaleViewport = true;
@@ -1947,6 +1971,9 @@ function setupVncViewer() {
1947
1971
  rfb.addEventListener('connect', () => {
1948
1972
  status.classList.add('hidden');
1949
1973
  retryCount = 0;
1974
+ reportBrowserViewerEvent('rfb-connected', {
1975
+ durationMs: Date.now() - connectStartedAt,
1976
+ });
1950
1977
  });
1951
1978
 
1952
1979
  rfb.addEventListener('disconnect', (e) => {
@@ -1954,6 +1981,11 @@ function setupVncViewer() {
1954
1981
  const detail = e.detail || {};
1955
1982
  const reason = detail.reason || (detail.clean === false ? 'Connection refused' : '');
1956
1983
  reportClientEvent('disconnect', reason || (detail.clean === false ? 'unclean-close' : 'normal'));
1984
+ reportBrowserViewerEvent('rfb-error', {
1985
+ durationMs: Date.now() - connectStartedAt,
1986
+ errorCode: detail.clean === false ? 'unclean-close' : 'normal',
1987
+ message: reason || '',
1988
+ });
1957
1989
  const reasonEl = status.querySelector('.status-reason');
1958
1990
  if (retryCount < MAX_RETRIES) {
1959
1991
  retryCount++;
@@ -2458,22 +2490,26 @@ function installService() {
2458
2490
  // Per-brand X display (Task 553). Same value used for the edge unit's
2459
2491
  // DISPLAY env (stamped via __VNC_DISPLAY__ a few lines down) so the main
2460
2492
  // brand service and the edge service agree on which display Chromium runs.
2461
- const VNC_DISPLAY = BRAND.vncDisplay ?? 99;
2462
- // Task 924 derive the three additional brand-scoped ports from
2463
- // `vncDisplay` when brand.json omits them. Operator-set explicit values
2464
- // in brand.json win; the derivation rule is the deterministic fallback.
2465
- // Mirrors paths.ts and vnc.sh so all four sites compute the same numbers
2466
- // regardless of which one reads brand.json first.
2467
- const VNC_OFFSET = VNC_DISPLAY - 99;
2468
- // Parenthesise the deterministic derivation. Operator-precedence already
2469
- // groups (offset + base) ahead of nullish-coalesce, so this is purely a
2470
- // textual change for readability and to satisfy Task 954's grep audit
2471
- // (the runtime path was the real silent default; these are install-time
2472
- // deterministic fallbacks but the audit is a single regex over the
2473
- // codebase).
2474
- const RFB_PORT = BRAND.rfbPort ?? (5900 + VNC_OFFSET);
2475
- const WEBSOCKIFY_PORT_BRAND = BRAND.websockifyPort ?? (6080 + VNC_OFFSET);
2476
- const CDP_PORT_BRAND = BRAND.cdpPort ?? (9222 + VNC_OFFSET);
2493
+ // Task 924 + 959 — brand.json (BRAND) is the single source of truth for
2494
+ // these fields at install time. The vncDisplay-derived offset rule lives
2495
+ // in the brand-creation tooling; at this point in the installer BRAND
2496
+ // already represents a parsed, validated brand manifest, and any missing
2497
+ // field is a brand-publish defect. Loud-fail rather than silently
2498
+ // substituting an offset (silent-fallback-masks-root-cause recurrence).
2499
+ if (typeof BRAND.vncDisplay !== "number") {
2500
+ console.error(`[create-maxy] error reason=cdp-port-unresolved brand=${BRAND.configDir} field=vncDisplay`);
2501
+ throw new Error(`brand.json missing required field: vncDisplay`);
2502
+ }
2503
+ const VNC_DISPLAY = BRAND.vncDisplay;
2504
+ for (const field of ["rfbPort", "websockifyPort", "cdpPort"]) {
2505
+ if (typeof BRAND[field] !== "number") {
2506
+ console.error(`[create-maxy] error reason=cdp-port-unresolved brand=${BRAND.configDir} field=${field}`);
2507
+ throw new Error(`brand.json missing required field: ${field}`);
2508
+ }
2509
+ }
2510
+ const RFB_PORT = BRAND.rfbPort;
2511
+ const WEBSOCKIFY_PORT_BRAND = BRAND.websockifyPort;
2512
+ const CDP_PORT_BRAND = BRAND.cdpPort;
2477
2513
  // Task 924/938 pre-flight — refuse to write service files if any of the
2478
2514
  // three brand-scoped ports is already held by a process that is NOT this
2479
2515
  // brand's own on-demand browser nor a peer brand's edge stack.
@@ -2671,7 +2707,7 @@ function installService() {
2671
2707
  if (!installAccountId) {
2672
2708
  throw new Error(`installService: no account discovered at ${INSTALL_DIR}/data/accounts/<uuid>/account.json — ` +
2673
2709
  `setupAccount() (seed-neo4j.sh) should have created one. Refusing to write a systemd unit ` +
2674
- `without ACCOUNT_ID; the boot validator would FATAL on every restart (Task 955).`);
2710
+ `without ACCOUNT_ID; the boot validator would FATAL on every restart.`);
2675
2711
  }
2676
2712
  const serviceFile = buildMaxyUnitFile({
2677
2713
  productName: BRAND.productName,
@@ -76,7 +76,7 @@ export function buildMaxyUnitFile(o) {
76
76
  // with no Environment=ACCOUNT_ID line — bootValidator would FATAL on
77
77
  // every restart with reason=missing. Throw at build time so the install
78
78
  // aborts loudly instead of bricking the boot loop.
79
- throw new Error("buildMaxyUnitFile: accountId is required — caller must resolve the on-disk account UUID before stamping the systemd unit (Task 955).");
79
+ throw new Error("buildMaxyUnitFile: accountId is required — caller must resolve the on-disk account UUID before stamping the systemd unit.");
80
80
  }
81
81
  const neo4jServiceDep = o.neo4jDedicated
82
82
  ? `neo4j-${o.brandHostname}.service`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/create-realagent",
3
- "version": "1.0.856",
3
+ "version": "1.0.859",
4
4
  "description": "Install Real Agent — Built for agents. By agents.",
5
5
  "bin": {
6
6
  "create-realagent": "./dist/index.js"
@@ -10,7 +10,7 @@
10
10
  "build": "tsc",
11
11
  "bundle": "node scripts/bundle.js",
12
12
  "test": "npm run build && node --test 'dist/__tests__/*.test.js'",
13
- "prepublishOnly": "bash ../../platform/scripts/verify-skill-tool-surface.sh && node ../../platform/ui/scripts/check-route-wiring.mjs && node ../../platform/ui/scripts/check-edge-admin-routes.mjs && npm run build && node --test 'dist/__tests__/*.test.js' && chmod +x dist/index.js && npm run bundle && node ../../platform/ui/scripts/check-bundle-node-imports.mjs --dir=./payload/server/public/assets"
13
+ "prepublishOnly": "bash ../../platform/scripts/verify-skill-tool-surface.sh && node ../../platform/scripts/check-no-task-id-leaks.mjs && node ../../platform/ui/scripts/check-route-wiring.mjs && node ../../platform/ui/scripts/check-edge-admin-routes.mjs && npm run build && node --test 'dist/__tests__/*.test.js' && chmod +x dist/index.js && npm run bundle && node ../../platform/ui/scripts/check-bundle-node-imports.mjs --dir=./payload/server/public/assets"
14
14
  },
15
15
  "files": [
16
16
  "dist",
@@ -43,7 +43,7 @@ if [ "$AGENT_TYPE" = "admin" ]; then
43
43
  # Patterns intentionally cover both absolute (*/...) and relative
44
44
  # (no leading slash) paths so an agent can't bypass via cwd-relative writes.
45
45
  */platform/lib/entitlement/*|platform/lib/entitlement/*|*/entitlement.json|entitlement.json)
46
- echo "Blocked: Admin agent cannot modify entitlement files at $FILE_PATH (Task 831). Effective tier and purchasedPlugins derive from a Rubytech-signed payload." >&2
46
+ echo "Blocked: Admin agent cannot modify entitlement files at $FILE_PATH. Effective tier and purchasedPlugins derive from a Rubytech-signed payload." >&2
47
47
  echo "[entitlement] tool-deny: tool=${TOOL_NAME} path=${FILE_PATH} field=entitlement-file" >&2
48
48
  exit 2
49
49
  ;;
@@ -76,7 +76,7 @@ if [ "$AGENT_TYPE" = "admin" ]; then
76
76
  ;;
77
77
  # Entitlement files via shell (Task 831) — same surface, blocked symmetrically
78
78
  *"platform/lib/entitlement/"*|*"entitlement.json"*)
79
- echo "Blocked: Admin agent cannot reference entitlement files via shell (Task 831)." >&2
79
+ echo "Blocked: Admin agent cannot reference entitlement files via shell." >&2
80
80
  echo "[entitlement] tool-deny: tool=Bash path=entitlement-file field=entitlement-file" >&2
81
81
  exit 2
82
82
  ;;
@@ -50,20 +50,30 @@ function resolveBrandConfig() {
50
50
  if (!brand.productName) {
51
51
  throw new Error(`brand.json at ${brandPath} is missing the productName field`);
52
52
  }
53
- // Task 924 — derive port set from `vncDisplay` ordinal offset when any
54
- // of rfbPort/websockifyPort/cdpPort is missing. Mirrors paths.ts's
55
- // deterministic fallback so an admin MCP probe sees the same per-brand
56
- // numbers the rest of the platform binds.
57
- const vncDisplay = typeof brand.vncDisplay === "number" ? brand.vncDisplay : 99;
58
- const offset = vncDisplay - 99;
53
+ // Task 924 + 959 brand.json is the single source of truth for the
54
+ // four brand-scoped port fields. An admin MCP probe must see the exact
55
+ // same numbers the rest of the platform binds; silent vncDisplay-derived
56
+ // fallback was a recurrence-class silent-fallback-masks-root-cause
57
+ // violation (Task 959) and is now loud-fail.
58
+ const brandLabel = String(brand.configDir).replace(/^\./, "");
59
+ if (typeof brand.vncDisplay !== "number") {
60
+ console.error(`[mcp:admin] error reason=cdp-port-unresolved brand=${brandLabel} path=${brandPath} field=vncDisplay json_keys=${Object.keys(brand).join(",")}`);
61
+ throw new Error(`brand.json at ${brandPath} missing required field: vncDisplay`);
62
+ }
63
+ for (const field of ["rfbPort", "websockifyPort", "cdpPort"]) {
64
+ if (typeof brand[field] !== "number") {
65
+ console.error(`[mcp:admin] error reason=cdp-port-unresolved brand=${brandLabel} path=${brandPath} field=${field} json_keys=${Object.keys(brand).join(",")}`);
66
+ throw new Error(`brand.json at ${brandPath} missing required field: ${field}`);
67
+ }
68
+ }
59
69
  return {
60
70
  configDir: brand.configDir,
61
71
  productName: brand.productName,
62
72
  commercialMode: brand.commercialMode === true,
63
- vncDisplay,
64
- rfbPort: typeof brand.rfbPort === "number" ? brand.rfbPort : 5900 + offset,
65
- websockifyPort: typeof brand.websockifyPort === "number" ? brand.websockifyPort : 6080 + offset,
66
- cdpPort: typeof brand.cdpPort === "number" ? brand.cdpPort : 9222 + offset,
73
+ vncDisplay: brand.vncDisplay,
74
+ rfbPort: brand.rfbPort,
75
+ websockifyPort: brand.websockifyPort,
76
+ cdpPort: brand.cdpPort,
67
77
  };
68
78
  }
69
79
  catch (err) {